package org.dfl.plugin.views.palette;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.dfl.core.model.transitions.CartesianProductTransition;
import org.dfl.core.model.transitions.EmptyMultisetTransition;
import org.dfl.core.model.transitions.EmptyTupleConstructionTransition;
import org.dfl.core.model.transitions.EqualTransition;
import org.dfl.core.model.transitions.ExecTransition;
import org.dfl.core.model.transitions.FlattenTransition;
import org.dfl.core.model.transitions.IdTransition;
import org.dfl.core.model.transitions.JarTransition;
import org.dfl.core.model.transitions.PiTransition;
import org.dfl.core.model.transitions.MultisetTransition;
import org.dfl.core.model.transitions.ToBooleanTransition;
import org.dfl.core.model.transitions.ToStringTransition;
import org.dfl.core.model.transitions.TupleConstructionTransition;
import org.dfl.core.model.transitions.UnionTransition;
import org.dfl.core.utils.Utils;
import org.dfl.messages.Messages;
import org.dfl.plugin.DflPlugin;
import org.dfl.plugin.editor.model.ConnectionModelElement;
import org.dfl.plugin.editor.model.NestedConnectionModelElement;
import org.dfl.plugin.editor.model.PlaceModelElement;
import org.dfl.plugin.editor.tools.PanningSelectionToolWithDblClickEntry;
import org.dfl.plugin.utils.WorkbenchUtil;
import org.dfl.plugin.views.palette.model.DefaultPaletteEntry;
import org.dfl.plugin.views.palette.model.FolderPaletteEntry;
import org.dfl.plugin.views.palette.model.PaletteEntry;
import org.dfl.plugin.views.palette.model.TransitionPaletteEntry;
import org.eclipse.gef.Tool;
import org.eclipse.gef.dnd.TemplateTransfer;
import org.eclipse.gef.palette.ConnectionCreationToolEntry;
import org.eclipse.gef.palette.ToolEntry;
import org.eclipse.gef.requests.CreationFactory;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSourceAdapter;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.part.ViewPart;

/**
 * view that provides tool to create places, transitions and edges
 * @author Piotr Wlodarczyk
 *
 */
public class PaletteView extends ViewPart {
	public static final String ID = "org.dfl.plugin.palette.paletteview"; //$NON-NLS-1$
	private static Collection<PaletteListener> paletteListeners = new ArrayList<PaletteListener>();
	private static PaletteEntry root;
	private static DefaultPaletteEntry defaultEntry;
	private TreeViewer treeViewer;
	private PaletteEntry selectedEntry;
	private Text filterField;
	private Button filterButton;

	/**
	 * The constructor.
	 */
	public PaletteView() {
	}

	public static void addPaletteListener(PaletteListener l) {
		if (!paletteListeners.contains(l)) {
			paletteListeners.add(l);
		}
	}

	public static void removePaletteListener(PaletteListener l) {
		paletteListeners.remove(l);
	}

	/*
	 * @see IWorkbenchPart#createPartControl(Composite)
	 */
	public void createPartControl(Composite parent) {
		final Listener filterListener = new Listener() {
			PaletteEntry lastEntry;
			String lastFilter = ""; //$NON-NLS-1$
			public void handleEvent(Event event) {
				String filter = filterField.getText().trim();
				if(!lastFilter.equals(filter)){
					lastEntry = null;
				}
				if(!filter.equals("")){ //$NON-NLS-1$
					PaletteEntry e = getNextPaletteEntry(filter,root,lastEntry);
					if(e!=null){
						lastEntry = e;
						treeViewer.expandToLevel(e, 0);
						treeViewer.setSelection(new StructuredSelection(e),true);
					}
				}
				lastFilter = filter;
			}

			private PaletteEntry getNextPaletteEntry(String filter,
					PaletteEntry root, PaletteEntry last) {
				List<PaletteEntry> palleteEntryList = getPalleteEntryList(root);
				boolean lastFound = (last == null);
				for(PaletteEntry e : palleteEntryList){
					if(e.getLabel().toLowerCase().lastIndexOf(filter.toLowerCase())>=0){
						if(lastFound){
							return e;
						}else if(e == last){
							lastFound = true;
						}
					}
				}
				if(lastFound && last!=null){
					return getNextPaletteEntry(filter,root,null);
				}
				return null;
			}

			private List<PaletteEntry> getPalleteEntryList(PaletteEntry root) {
				List<PaletteEntry> res = new ArrayList<PaletteEntry>();
				if(root instanceof TransitionPaletteEntry) res.add(root);
				else if(root instanceof FolderPaletteEntry){
					Collection<PaletteEntry> childs = root.getChildren();
					for(PaletteEntry child : childs){
						res.addAll(getPalleteEntryList(child));
					}
				}
				return res;
			}
		};
		
		GridLayout thisLayout = new GridLayout();
		thisLayout.numColumns = 2;
		thisLayout.marginWidth = 0;
		parent.setLayout(thisLayout);
		
		filterField = new Text(parent, SWT.BORDER);
		GridData filterFieldlayoutData = new GridData();
		filterFieldlayoutData.grabExcessHorizontalSpace = true;
		filterFieldlayoutData.horizontalAlignment = GridData.FILL;
		filterField.setLayoutData(filterFieldlayoutData);
		filterField.addListener(SWT.KeyDown, new Listener() {
		    public void handleEvent(Event event) {
		        if( event.keyCode == SWT.CR ) {
		        	filterListener.handleEvent(event);
		        }
		    }
		});
		
		filterButton = new Button(parent, SWT.NONE);
		filterButton.setImage(new Image(Display.getCurrent(),DflPlugin.getImageDescriptor("icons/eclipse/search.gif").getImageData())); //$NON-NLS-1$
		filterButton.addListener(SWT.Selection, filterListener);
		
		
		// Create the tree viewer as a child of the composite parent
		treeViewer = new TreeViewer(parent);
		treeViewer.setContentProvider(new PaletteContentProvider());
		treeViewer.setLabelProvider(new PaletteLabelProvider());
		treeViewer.setUseHashlookup(true);

		// layout the tree viewer below the text field
		GridData layoutData = new GridData();
		layoutData.grabExcessHorizontalSpace = true;
		layoutData.grabExcessVerticalSpace = true;
		layoutData.horizontalAlignment = GridData.FILL;
		layoutData.verticalAlignment = GridData.FILL;
		layoutData.horizontalSpan = 2;
		treeViewer.getControl().setLayoutData(layoutData);

		// Create menu, toolbars, filters, sorters.
		hookListeners();

		treeViewer.setInput(root);
		treeViewer.collapseAll();
		createToolbar();
		createContextMenu();
	}
	
	/**
     * Create toolbar.
     */
    private void createToolbar() {
        IToolBarManager mgr = getViewSite().getActionBars().getToolBarManager();
		mgr.add(new Action() {
			@Override
			public String getDescription() {
				return Messages.PaletteView_4;
			}
			
			@Override
			public ImageDescriptor getImageDescriptor() {
				return DflPlugin
						.getImageDescriptor("icons/eclipse/collapseall.gif"); //$NON-NLS-1$
			}

			@Override
			public void run() {
				treeViewer.collapseAll();
			}
		});
		
		mgr.add(new Action() {
			@Override
			public String getDescription() {
				return Messages.PaletteView_6;
			}
			
			@Override
			public ImageDescriptor getImageDescriptor() {
				return DflPlugin
						.getImageDescriptor("icons/eclipse/expandall.gif"); //$NON-NLS-1$
			}

			@Override
			public void run() {
				treeViewer.expandAll();
			}
		});
    }

    private void createContextMenu() {
        // Create menu manager.
		MenuManager menuMgr = new MenuManager();
		menuMgr.setRemoveAllWhenShown(true);
		menuMgr.addMenuListener(new IMenuListener() {
			public void menuAboutToShow(IMenuManager mgr) {
				fillContextMenu(mgr);
			}
		});

		// Create menu.
		Menu menu = menuMgr.createContextMenu(treeViewer.getControl());
		treeViewer.getControl().setMenu(menu);

		// Register menu for extension.
		getSite().registerContextMenu(menuMgr, treeViewer);
    }
	protected void fillContextMenu(IMenuManager mgr) {
		mgr.add(new Action(Messages.PaletteView_8){
			@Override
			public String getDescription() {
				return Messages.PaletteView_9;
			}
			
			@Override
			public ImageDescriptor getImageDescriptor() {
				return DflPlugin.getImageDescriptor("icons/eclipse/update.gif"); //$NON-NLS-1$
			}
			
			@SuppressWarnings("unchecked") //$NON-NLS-1$
			@Override
			public boolean isEnabled() {
				IStructuredSelection sel = (IStructuredSelection)treeViewer.getSelection();
				Iterator iter = sel.iterator();
				while (iter.hasNext()) {
					Object o = iter.next();
					if(o instanceof TransitionPaletteEntry &&
						((TransitionPaletteEntry)o).getDescription()==null){
						return true;
					}
				}
				return false;
			}
			@SuppressWarnings("unchecked") //$NON-NLS-1$
			@Override
			public void run() {
				final IStructuredSelection sel = (IStructuredSelection)treeViewer.getSelection();
				new Thread(){
					@Override
					public void run() {
						Iterator iter = sel.iterator();
						while (iter.hasNext()) {
							final Object o = iter.next();
							if(o instanceof TransitionPaletteEntry){
								((TransitionPaletteEntry)o).fetchDescription();
								Utils.execAsynch(new Runnable(){
									public void run() {
										treeViewer.refresh(o);
									}});
								
							}
						}
					}
				}.start();
			}
		});
	}

	private void hookListeners() {
		treeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
			public void selectionChanged(SelectionChangedEvent event) {
				// if the selection is empty clear the label
				if (event.getSelection().isEmpty()) {
					setSelectedEntry(null);
					return;
				}
				if (event.getSelection() instanceof IStructuredSelection) {
					IStructuredSelection selection = (IStructuredSelection) event
							.getSelection();
					PaletteEntry entry = (PaletteEntry) selection.iterator()
							.next();
					setSelectedEntry(entry);
				}
			}
		});
		treeViewer.addDragSupport(DND.DROP_COPY,
				new Transfer[] { TemplateTransfer.getInstance() },
				new DragSourceAdapter() {
					@Override
					public void dragStart(DragSourceEvent arg0) {
						TemplateTransfer.getInstance().setTemplate(
								getSelectedEntry().getTransferTemplate());
						super.dragStart(arg0);
					}

					@Override
					public void dragSetData(DragSourceEvent event) {
						event.data = TemplateTransfer.getInstance()
								.getTemplate();
					}
				});
	}

	static {
		root = new FolderPaletteEntry(Messages.PaletteView_tools);

		ToolEntry tool = null;

		// Add a selection tool
		tool = new PanningSelectionToolWithDblClickEntry();
		defaultEntry = new DefaultPaletteEntry(tool);
		root.addChild(defaultEntry);

		// Add marquee tool
		// tool = new MarqueeToolEntry();
		// root.addChild(new DefaultPaletteEntry(tool));

		// Add connection
		tool = new ConnectionCreationToolEntry(Messages.PaletteView_connectName, Messages.PaletteView_connectTip,
				new CreationFactory() {
					public Object getNewObject() {
						return null;
					}

					public Object getObjectType() {
						return ConnectionModelElement.class;
					}
				}, DflPlugin.getImageDescriptor("icons/connection16.gif"), DflPlugin //$NON-NLS-1$
						.getImageDescriptor("icons/connection16.gif")); //$NON-NLS-1$
		root.addChild(new DefaultPaletteEntry(tool));

		// Add extended connection
		tool = new ConnectionCreationToolEntry(Messages.PaletteView_xConnectName, Messages.PaletteView_xconnectTip,
				new CreationFactory() {
					public Object getNewObject() {
						return null;
					}

					public Object getObjectType() {
						return NestedConnectionModelElement.class;
					}
				}, DflPlugin.getImageDescriptor("icons/connection16.gif"), DflPlugin //$NON-NLS-1$
						.getImageDescriptor("icons/connection16.gif")); //$NON-NLS-1$
		root.addChild(new DefaultPaletteEntry(tool));

		// add place palette element
		root.addChild(new DefaultPaletteEntry(Messages.PaletteView_place, "icons/place16.gif", //$NON-NLS-2$ //$NON-NLS-1$
				PlaceModelElement.class, false));

		// lokalne tranzycje
		getLocalTree(root);
	}

	/**
	 * budowanie drzewka tranzycji lokalnych tutaj dodawac kolejne tranzycje :)
	 * 
	 * @return
	 */
	private static PaletteEntry getLocalTree(PaletteEntry root) {
		PaletteEntry dfl = new FolderPaletteEntry(Messages.PaletteView_coreT);
		root.addChild(dfl);
		// add transitions
		dfl.addChild(new TransitionPaletteEntry(IdTransition.class));
		dfl.addChild(new TransitionPaletteEntry(MultisetTransition.class));
		dfl
				.addChild(new TransitionPaletteEntry(
						EmptyMultisetTransition.class));
		dfl.addChild(new TransitionPaletteEntry(
				EmptyTupleConstructionTransition.class));
		dfl.addChild(new TransitionPaletteEntry(
				TupleConstructionTransition.class));
		dfl.addChild(new TransitionPaletteEntry(PiTransition.class));
		dfl.addChild(new TransitionPaletteEntry(UnionTransition.class));
		dfl.addChild(new TransitionPaletteEntry(FlattenTransition.class));
		dfl.addChild(new TransitionPaletteEntry(
				CartesianProductTransition.class));
		dfl.addChild(new TransitionPaletteEntry(EqualTransition.class));


		PaletteEntry convert = new FolderPaletteEntry(Messages.PaletteView_typeT);
		root.addChild(convert);
		convert.addChild(new TransitionPaletteEntry(ToStringTransition.class));
		convert.addChild(new TransitionPaletteEntry(ToBooleanTransition.class));
		
		PaletteEntry other = new FolderPaletteEntry(Messages.PaletteView_otherT);
		root.addChild(other);
		other.addChild(new TransitionPaletteEntry(ExecTransition.class));
		other.addChild(new TransitionPaletteEntry(JarTransition.class));

		return root;
	}

	/*
	 * @see IWorkbenchPart#setFocus()
	 */
	public void setFocus() {
	}

	protected PaletteEntry getSelectedEntry() {
		return selectedEntry;
	}

	protected void setSelectedEntry(PaletteEntry selectedEntry) {
		if (this.selectedEntry != selectedEntry) {
			this.selectedEntry = selectedEntry;
			if (selectedEntry != null) {
				fireSelectedToolChanged(getSelectedEntry().getTool());
			} else {
				fireSelectedToolChanged(null);
			}
		}
	}

	private void fireSelectedToolChanged(Tool tool) {
		for (PaletteListener l : paletteListeners) {
			l.selectedToolChanged(tool);
		}
	}
	
	public void selectDefaultEntry(){
		setSelectedEntry(defaultEntry);
		treeViewer.setSelection(new StructuredSelection(defaultEntry),true);
	}

	public static void addTreeElement(PaletteEntry entry) {
		root.addChild(entry);
	}
	
	public static PaletteView getView(){
		return (PaletteView) WorkbenchUtil.getViewById(ID);
	}
	
	public void refresh(Object o){
		treeViewer.refresh(o);
	}
}